package com.fakeDYChat.common.websocket.service.impl;

import cn.hutool.core.util.RandomUtil;
import cn.hutool.json.JSONUtil;
import com.fakeDYChat.common.common.event.UserOnlineEvent;
import com.fakeDYChat.common.common.utils.NettyUtil;
import com.fakeDYChat.common.user.dao.UserDao;
import com.fakeDYChat.common.user.domain.dto.WSChannelExtraDTO;
import com.fakeDYChat.common.user.domain.entity.User;
import com.fakeDYChat.common.user.domain.enums.RoleEnum;
import com.fakeDYChat.common.user.service.IRoleService;
import com.fakeDYChat.common.user.service.LoginService;
import com.fakeDYChat.common.websocket.domain.vo.resp.WSBaseResp;
import com.fakeDYChat.common.websocket.service.adapter.WSAdapter;
import com.fakeDYChat.common.websocket.service.WebSocketService;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.mp.api.WxMpQrcodeService;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.result.WxMpQrCodeTicket;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.util.Date;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import static com.fakeDYChat.common.common.thread.ThreadPoolNameConstant.WS_EXECUTOR;

@Slf4j
@Service
public class WebSocketServiceImpl implements WebSocketService {
    //微信接口请求服务
    private final WxMpService wxMpService;
    //用户消息操作服务
    private final UserDao userDao;
    private final LoginService loginService;
    private final ApplicationEventPublisher applicationEventPublisher;
    private final IRoleService iRoleService;
    private final ThreadPoolTaskExecutor threadPoolExecutor;

    //最大的登录码保存数
    public static final int MAXIMUM_SIZE = 1000;
    //最长的登录码保存时间
    public static final Duration EXPIRE_TIME = Duration.ofSeconds(60);
    private static final Cache<Integer, ChannelHandlerContext> CODE_CTX_MAP = Caffeine.newBuilder()
            .maximumSize(MAXIMUM_SIZE)
            .expireAfterWrite(EXPIRE_TIME)
            .build();

    //所有已连接的websocket连接列表和一些额外参数
    private static final ConcurrentMap<ChannelHandlerContext, WSChannelExtraDTO> ONLINE_WS_MAP = new ConcurrentHashMap<>();

    public WebSocketServiceImpl(WxMpService wxMpService,
                                UserDao userDao,
                                LoginService loginService,
                                ApplicationEventPublisher applicationEventPublisher,
                                IRoleService iRoleService,
                                @Qualifier(WS_EXECUTOR) ThreadPoolTaskExecutor threadPoolExecutor) {
        this.wxMpService = wxMpService;
        this.userDao = userDao;
        this.loginService = loginService;
        this.applicationEventPublisher = applicationEventPublisher;
        this.iRoleService = iRoleService;
        this.threadPoolExecutor = threadPoolExecutor;
    }

    /**
     * 用户请求二维码逻辑
     *
     * @param ctx
     * @return void
     * @author Tang
     * @date 2023/12/14 9:00
     **/
    @Override
    public void handleLoginReq(ChannelHandlerContext ctx) throws WxErrorException {
        //todo 生成登陆码逻辑
        //生成登录码
        Integer code = generateLoginCode(ctx);
        //请求微信生成带参二维码
        WxMpQrCodeTicket codeTicket = getWxMpQrCodeTicket(code);
        //返回给前端
        sendMsg(ctx, WSAdapter.buildLoginResp(codeTicket));
    }

    /**
     * 用户扫码后推送
     *
     * @param code
     * @return void
     * @author Tang
     * @date 2023/12/11 10:30
     **/
    @Override
    public void handleScanSuccess(Integer code) {
        ChannelHandlerContext ctx = CODE_CTX_MAP.getIfPresent(code);
        if (Objects.isNull(ctx)) {
            return;
        }
        sendMsg(ctx, WSAdapter.buildScanSuccessResp());
    }

    /**
     * 处理ws连接
     *
     * @param ctx
     * @return void
     * @author Tang
     * @date 2023/12/10 15:36
     **/
    @Override
    public void connect(ChannelHandlerContext ctx) {
        ONLINE_WS_MAP.put(ctx, new WSChannelExtraDTO());
    }

    @Override
    public void offline(ChannelHandlerContext ctx) {
        WSChannelExtraDTO remove = ONLINE_WS_MAP.remove(ctx);
        //todo 用户下线通知
    }

    @Override
    public void scanLoginSuccess(Integer eventKey, Long uid) {
        ChannelHandlerContext ctx = CODE_CTX_MAP.getIfPresent(eventKey);
        CODE_CTX_MAP.invalidate(eventKey);
        if (Objects.isNull(ctx)) {
            return;
        }
        //绑定连接和uid
        WSChannelExtraDTO wsChannelExtraDTO = ONLINE_WS_MAP.get(ctx);
        if (Objects.nonNull(wsChannelExtraDTO)) {
            wsChannelExtraDTO.setUid(uid);
        }
        //登录逻辑
        User user = userDao.getById(uid);
        String token = loginService.login(uid);
        loginSuccess(ctx, user, token);
    }

    /**
     * 认证
     *
     * @param ctx
     * @param token
     * @return void
     * @author Tang
     * @date 2023/12/22 9:54
     **/
    @Override
    public void authorized(ChannelHandlerContext ctx, String token) {
        Long validUid = loginService.getValidUid(token);
        if (Objects.isNull(validUid)) {
            //token过期
            sendMsg(ctx, WSAdapter.buildInvalidTokenResp());
        } else {
            User user = userDao.getById(validUid);
            loginSuccess(ctx, user, token);
        }
    }

    /**
     * 向在线的用户推送消息
     *
     * @param msg 消息
     * @author Tang
     * @date 2024/1/12 20:42
     **/
    @Override
    public void sendMsgToAll(WSBaseResp<?> msg) {
        ONLINE_WS_MAP.forEach(((channel, wsDto) -> {
            threadPoolExecutor.submit(() -> {
                sendMsg(channel, msg);
            });
        }));
    }

    /**
     * 登录成功逻辑
     *
     * @param ctx   用户通信通道
     * @param user  用户信息
     * @param token 用户token
     * @author Tang
     * @date 2024/1/7 20:01
     **/
    private void loginSuccess(ChannelHandlerContext ctx, User user, String token) {
        //更新对应连接的uid
        WSChannelExtraDTO wsChannelExtraDTO = ONLINE_WS_MAP.get(ctx);
        wsChannelExtraDTO.setUid(user.getId());
        //推送登录成功消息
        sendMsg(ctx, WSAdapter.buildLoginSuccess(user, token, iRoleService.hasPower(user.getId(), RoleEnum.CHAT_MANAGER)));
        //上线通知
        //最后操作时间
        user.setLastOptTime(new Date());
        //刷新用户ip
        user.refreshIp(NettyUtil.getAttr(ctx.channel(), NettyUtil.IP));
        applicationEventPublisher.publishEvent(new UserOnlineEvent(this, user));
    }

    /**
     * 获取微信二维码
     *
     * @param code
     * @return me.chanjar.weixin.mp.bean.result.WxMpQrCodeTicket
     * @author Tang
     * @date 2023/12/10 15:15
     **/
    private WxMpQrCodeTicket getWxMpQrCodeTicket(Integer code) throws WxErrorException {
        WxMpQrcodeService qrcodeService = wxMpService.getQrcodeService();
        return qrcodeService.qrCodeCreateTmpTicket(code, (int) EXPIRE_TIME.getSeconds());
    }

    /**
     * 生成随机的登录码
     *
     * @param ctx
     * @return java.lang.Integer
     * @author Tang
     * @date 2023/12/9 22:19
     **/
    private Integer generateLoginCode(ChannelHandlerContext ctx) {
        Integer code;
        do {
            code = RandomUtil.randomInt(Integer.MAX_VALUE);
        } while (Objects.nonNull(CODE_CTX_MAP.asMap().putIfAbsent(code, ctx)));
        return code;
    }

    /**
     * 指定用户推送微信二维码
     *
     * @param ctx
     * @param resp
     * @return void
     * @author Tang
     * @date 2023/12/10 15:18
     **/
    private void sendMsg(ChannelHandlerContext ctx, WSBaseResp<?> resp) {
        ctx.channel().writeAndFlush(new TextWebSocketFrame(JSONUtil.toJsonStr(resp)));
    }
}
